#include "my_time.h"
#include "esp_netif_sntp.h"
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "my_background.h"
#include <string.h>

#define TAG "MyTime"

MyTime::MyTime()
    : timer_handle_(nullptr)
    , flag_need_clean_(false)
    , storage_(MyStorage<TaskSaveInfo>("Time", DECLARE_META(TaskSaveInfo,
        FIELD(TaskSaveInfo, name),
        FIELD(TaskSaveInfo, callback_key),
        FIELD(TaskSaveInfo, callback_arg),
        FIELD(TaskSaveInfo, type),
        FIELD(TaskSaveInfo, interval_sec),
        FIELD(TaskSaveInfo, start_run_time),
        FIELD(TaskSaveInfo, remain_runs)
        )))
    , callback_map_({})
{
    setenv("TZ", "CST-8", 1);
    tzset();
    #if CONFIG_LWIP_SNTP_MAX_SERVERS == 1
        esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("ntp.aliyun.com");
    #else
        esp_sntp_config_t config = {
            .smooth_sync = true,
            .server_from_dhcp = false,
            .wait_for_sync = true,
            .start = true,
            .sync_cb = nullptr,
            .renew_servers_after_new_IP = false,
            .ip_event_to_renew = IP_EVENT_STA_GOT_IP,
            .index_of_first_server = 0,
            .num_of_servers = 2,
            .servers = {
                "ntp.aliyun.com",
                "pool.ntp.org"
            }
        };
    #endif
    esp_netif_sntp_init(&config);
    sntp_set_sync_interval(60 * 60 * 1000); // 设置时间同步间隔
    esp_netif_sntp_sync_wait(3000 / portTICK_PERIOD_MS);

    // 需要一个Load NVS的过程，以加载那些从UI添加的任务
    storage_.ForeachReadOnly([this](const TaskSaveInfo& info){
        auto ret = cron_list_.construct([&info](TaskCronInfo* item){
            new (item) TaskCronInfo(info);
        });
        if (!ret) {
            ESP_LOGE(TAG, "Restore from storage failed.");
        }

        return ret;
    }, false);

    // 创建定时器
    timer_handle_ = xTimerCreate(
        "MyTime",
        pdMS_TO_TICKS(1000),
        pdTRUE,
        (void*) this,
        [](TimerHandle_t xTimer) {

            auto& timer = *reinterpret_cast<MyTime*>(pvTimerGetTimerID(xTimer));
            auto& bg = MyBackground::GetInstance();

            {
                std::lock_guard<std::mutex> lock(timer.mutex_);
                time(&timer.now_);
                localtime_r(&(timer.now_), &(timer.current_time_));
            }


            if (timer.current_time_.tm_sec == 0) {
                // 创建一个后台检测任务？
                bg.Schedule([&timer](void* arg){
                    timer.CheckAndExecuteTasks();
                    char time_buf[20];
                    ESP_LOGI(TAG, "Current time: %s", timer.GetTime(time_buf, 20, "%Y-%m-%d %H:%M:%S"));
                }, "BgCheck");
            } else if (timer.current_time_.tm_sec == 30) {
                if (timer.flag_need_clean_) {
                    // 创建一个清理任务
                    bg.Schedule([&timer](void* arg){
                        timer.ClearRunFlags();
                    }, "BgClear");
                }
            }
        }
    );

    if (timer_handle_) {
        xTimerStart(timer_handle_, 0);
    }
}

MyTime::~MyTime()
{
    xTimerDelete(timer_handle_, 0);
}

bool MyTime::AddYearlyTask(Task&& info, AlarmCallback cb, bool need_save)
{
    TaskCronInfo task(info);
    task.type = kYearly;
    return AddTask(task, cb, need_save);
}
bool MyTime::AddMonthlyTask(Task&& info, AlarmCallback cb, bool need_save)
{
    TaskCronInfo task(info);
    task.type = kMonthly;
    return AddTask(task, cb, need_save);
}
bool MyTime::AddDailyTask(Task&& info, AlarmCallback cb, bool need_save)
{
    TaskCronInfo task(info);
    task.type = kDaily;
    return AddTask(task, cb, need_save);
}
bool MyTime::AddHourlyTask(Task&& info, AlarmCallback cb, bool need_save)
{
    TaskCronInfo task(info);
    task.type = kHourly;
    return AddTask(task, cb, need_save);
}
bool MyTime::AddMinuteTask(Task&& info, AlarmCallback cb, bool need_save)
{
    TaskCronInfo task(info);
    task.type = kMinute;
    return AddTask(task, cb, need_save);
}
bool MyTime::AddWeeklyTask(Task&& info, AlarmCallback cb, bool need_save)
{
    TaskCronInfo task(info);
    task.type = kWeekly;;
    return AddTask(task, cb, need_save);
}
bool MyTime::AddCustomTask(Task&& info, AlarmCallback cb, bool need_save)
{
    TaskCronInfo task(info);
    task.type = kCustom;
    return AddTask(task, cb, need_save);
}

/// @brief 删除一个任务（包括存储在NVS中的记录）
/// @param task_name 
void MyTime::DelTask(const std::string& task_name)
{
    std::lock_guard<std::mutex> lock(mutex_);
    cron_list_.remove_if([&task_name](const TaskCronInfo& task) -> bool {
        return task.name == task_name;
    });
    storage_.Del([&task_name](const TaskSaveInfo& task) -> bool {
        return task.name == task_name;
    });
}

/// @brief 清空任务列表
void MyTime::ClearTaskList()
{
    std::lock_guard<std::mutex> lock(mutex_);
    cron_list_.clear();
    storage_.Clear();
}

/// @brief 查找一个任务是否存在
/// @param task_name 任务名
/// @return 返回任务的指针或nullptr
const Task* MyTime::FindTask(const std::string& task_name)
{
    std::lock_guard<std::mutex> lock(mutex_);
    for (const auto& task : cron_list_) {
        if (task.name == task_name) {
            return &task;
        }
    }

    return nullptr;
}

/// @brief 生成一个time_t格式的时间
/// @param year 
/// @param month 仅支持1-12的表示，超出范围时强制为1
/// @param day 
/// @param hour 24小时制
/// @param minute 
/// @return 
time_t MyTime::MakeTime(int year, int month, int day, int hour, int minute)
{
    struct tm time_info = { 0 };

    if (month > 12 || month < 1) {
        month = 1;
        ESP_LOGE(TAG, "Support month: 1-12");
        return mktime(&time_info);
    }

    time_info.tm_year = year >= 1900 ? year - 1900 : year;
    time_info.tm_mon = month - 1;
    time_info.tm_mday = day;
    time_info.tm_hour = hour;
    time_info.tm_min = minute;
    time_info.tm_sec = 0;
    time_info.tm_isdst = -1;

    setenv("TZ", "CST-8", 1);
    tzset();
    auto time = mktime(&time_info);
    ESP_LOGW(TAG, "Generate time:%ld", time);

    return time;
}



/// @brief 回调注册函数
/// @param key 调用回调函数时的键名
/// @param cb 回调函数指针
bool MyTime::RegisterCallbackWithoutLock(const std::string& key, AlarmCallback cb)
{

    auto it = callback_map_.find(key);
    if (it != callback_map_.end()) {
        ESP_LOGW(TAG, "This key=%s is already registered.", key.c_str());
        return false;
    }
    callback_map_.emplace(key, cb);

    return true;
}

/// @brief 添加（修改）一个任务
bool MyTime::AddTask(TaskCronInfo& task, AlarmCallback cb, bool need_save)
{
    std::lock_guard<std::mutex> lock(mutex_);

    // 任务队列检查
    if (cron_list_.size() >= CONFIG_MAX_TASKS) {
        ESP_LOGW(TAG, "Add cron task failed, task list is full.");
        return false;
    }

    // 注册回调检查
    if (cb != nullptr) {
        RegisterCallbackWithoutLock(task.callback_key, cb);
    }

    // 保存任务
    if (need_save) {
        if(!SaveTask(task)) {
            ESP_LOGE(TAG, "Save task failed.");
        } else {
            ESP_LOGE(TAG, "Save success, time start:%ld", task.start_run_time);
        }
    }

    // 修改任务
    for (auto& cron_task : cron_list_) {
        if (0 == strcmp(cron_task.name, task.name)) {
            cron_task = task;                
            ESP_LOGW(TAG, "Task exist, modify it.");
            return true;
        }
    }
    return cron_list_.push_back(std::move(task));
}

/// @brief 保存一个任务至NVS中
/// @param save_task 任务相关信息
/// @return 
bool MyTime::SaveTask(const TaskCronInfo& save_task)
{
    TaskSaveInfo info = save_task;
    info.type = save_task.type;
    
    std::string name = save_task.name;
    auto ret = storage_.Upsert([&name](TaskSaveInfo& info){
        return info.name == name;
    }, info);

    return ret;
}

/// @brief 通过键名调用函数
/// @param key 键名
/// @param arg 函数参数
void MyTime::CallFunByKey(const std::string key, const std::string arg)
{
    auto it = callback_map_.find(key);
    if (it == callback_map_.end()) {
        ESP_LOGE(TAG, "Callback not registered! key=%s", key.c_str());
        return;
    }
    if (it->second) {
        (it->second)(arg);
    }
}

/// @brief 检查并执行任务
void MyTime::CheckAndExecuteTasks()
{
    std::lock_guard<std::mutex> lock(mutex_);

    for (auto it = cron_list_.begin(); it != cron_list_.end();) {
        auto& task = *it;
        it++; 
        if (ShouldExecuteTask(task)) {
            ExecuteTask(task);
        }
    }
}

/// @brief 判断任务是否需运行
inline bool MyTime::ShouldExecuteTask(const TaskCronInfo& task)
{
    if (task.start_run_time > now_) {
        return false;
    }
    struct tm task_time;
    localtime_r(&(task.start_run_time), &task_time);

    switch (task.type) {
      case kYearly: 
        return (task_time.tm_mon == current_time_.tm_mon)
            && (task_time.tm_mday == current_time_.tm_mday)
            && (task_time.tm_hour == current_time_.tm_hour)
            && (task_time.tm_min == current_time_.tm_min);
      case kMonthly:
        return (task_time.tm_mday == current_time_.tm_mday)
            && (task_time.tm_hour == current_time_.tm_hour)
            && (task_time.tm_min == current_time_.tm_min);
      case kDaily:
        return (task_time.tm_hour == current_time_.tm_hour)
            && (task_time.tm_min == current_time_.tm_min);
      case kHourly:
        return (task_time.tm_min == current_time_.tm_min);
      case kMinute:
        return current_time_.tm_sec == 0;
      case kWeekly:
        return (task_time.tm_wday == current_time_.tm_wday);
      case kCustom:
        return (now_ - task.start_run_time)%task.interval_sec == 0;
    }
    ESP_LOGE(TAG, "Unsupport CronPeriod type!");

    return false;
}

/// @brief 运行任务
inline void MyTime::ExecuteTask(TaskCronInfo& task)
{
    task.has_run = true;

    CallFunByKey(task.callback_key, task.callback_arg);

    // 根据周期次数决定是否删除任务
    if (task.remain_runs > 0) {
        task.remain_runs --;
        if(task.remain_runs == 0) {
            // 删除存储中的任务
            std::string name = task.name;
            storage_.Del([&name](const TaskSaveInfo& info) {
                return name == info.name;
            });
            // 删除列表中的任务
            cron_list_.remove_if([&name](const TaskCronInfo& t) {
                return name == t.name;
            });
        }
    }
}

/// @brief 清除运行标志
void MyTime::ClearRunFlags()
{
    std::lock_guard<std::mutex> lock(mutex_);
    ESP_LOGI(TAG, "Ready to clear task run flag.");
    for (auto& task : cron_list_) {
        if (task.has_run) {
            task.has_run = false;
            ESP_LOGI(TAG, "Clear task: %s run flag.", task.name);
        }
    }
    flag_need_clean_ = false;
}

